#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <iostream>
/*!
**
** Copyright (c) 2014 by John W. Ratcliff mailto:jratcliffscarab@gmail.com
**
**
** The MIT license:
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is furnished
** to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all
** copies or substantial portions of the Software.

** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


**
** If you find this code snippet useful; you can tip me at this bitcoin address:
**
** BITCOIN TIP JAR: "1BT66EoaGySkbY9J6MugvQRhMMXDwPxPya"
**



*/

#ifdef _MSC_VER
#pragma warning(disable:4996 4100)
#endif

#include "PlatformConfigHACD.h"
#include "wavefront.h"
#include "HACD.h"
#include "RenderDebugBinding.h"

namespace HACD
{
	HACD_API *gHACD = NULL;
};

void getRenderDebugDLL(void)
{
	const char *dllName=NULL;
#ifdef NV_X64
#ifdef _DEBUG
	dllName = "RenderDebug_x64.dll";
#else
	dllName = "RenderDebug_x64.dll";
#endif
#else
#ifdef _DEBUG
	dllName = "RenderDebug_x86.dll";
#else
	dllName = "RenderDebug_x86.dll";
#endif
#endif
	printf("Loading RenderDebug DLL\r\n");
	RENDER_DEBUG::RenderDebug::Desc desc;
	desc.dllName = dllName;
	desc.recordFileName = "TestHACD.rec";
	desc.runMode = RENDER_DEBUG::RenderDebug::RM_CLIENT_OR_FILE;
	gRenderDebug = RENDER_DEBUG::createRenderDebug(desc);
}


float getFloatArg(int arg,int argc,const char **argv)
{
	float ret = 0;
	if ( arg < argc )
	{
		ret = (float)atof(argv[arg]);
	}
	else
	{
		printf("Error: Missing input argument value at argument location %d.\r\n",arg+1);
	}
	return ret;
}

int getIntArg(int arg,int argc,const char **argv)
{
	int ret = 0;
	if ( arg < argc )
	{
		ret = atoi(argv[arg]);
	}
	else
	{
		printf("Error: Missing input argument value at argument location %d.\r\n",arg+1);
	}
	return ret;
}

void includeMinMax(const float *p,float *bmin,float *bmax)
{
	if ( p[0] < bmin[0] ) bmin[0] = p[0];
	if ( p[1] < bmin[1] ) bmin[1] = p[1];
	if ( p[2] < bmin[2] ) bmin[2] = p[2];
	if ( p[0] > bmax[0] ) bmax[0] = p[0];
	if ( p[1] > bmax[1] ) bmax[1] = p[1];
	if ( p[2] > bmax[2] ) bmax[2] = p[2];
}

void debugVisualize(WavefrontObj &w)
{
	// Ok, first render the original source mesh.
	gRenderDebug->debugMessage("Input Mesh has %d triangles and %d vertices.", w.mTriCount, w.mVertexCount );

	gRenderDebug->addToCurrentState(RENDER_DEBUG::DebugRenderState::SolidWireShaded);
	gRenderDebug->setCurrentColor(0xFFFF00,0xFFFFFF);
	for (int32_t i=0; i<w.mTriCount; i++)
	{
		uint32_t i1 = w.mIndices[i*3+0];
		uint32_t i2 = w.mIndices[i*3+1];
		uint32_t i3 = w.mIndices[i*3+2];
		const float *p1 = &w.mVertices[i1*3];
		const float *p2 = &w.mVertices[i2*3];
		const float *p3 = &w.mVertices[i3*3];
		gRenderDebug->debugTri(p3,p2,p1);
	}

	gRenderDebug->render(1.0f/60.0f,NULL);

	{
		// Next frame, render all of the convex hulls
		uint32_t hullCount = HACD::gHACD->getHullCount();
		uint32_t c = RENDER_DEBUG::DebugColors::Red;
		gRenderDebug->debugMessage("Convex Decomposition Produced %d convex hulls", hullCount );
		for (uint32_t i=0; i<hullCount; i++)
		{
			const HACD::HACD_API::Hull *h = HACD::gHACD->getHull(i);

			if ( h )
			{
				uint32_t color = gRenderDebug->getDebugColor((RENDER_DEBUG::DebugColors::Enum)c);
				gRenderDebug->setCurrentColor(color,0xFFFFFF);
				for (uint32_t i=0; i<h->mTriangleCount; i++)
				{
					uint32_t i1 = h->mIndices[i*3+0];
					uint32_t i2 = h->mIndices[i*3+1];
					uint32_t i3 = h->mIndices[i*3+2];
					const float *p1 = &h->mVertices[i1*3];
					const float *p2 = &h->mVertices[i2*3];
					const float *p3 = &h->mVertices[i3*3];
					gRenderDebug->debugTri(p1,p2,p3);
				}
				c++;
				if ( c == RENDER_DEBUG::DebugColors::NUM_COLORS )
				{
					c = RENDER_DEBUG::DebugColors::Red;
				}
			}
		}
		gRenderDebug->render(1.0f/60.0f,NULL);
	}

	{
		// Next frame, render all of the convex hulls in a loop
		uint32_t hullCount = HACD::gHACD->getHullCount();
		gRenderDebug->debugMessage("Convex Decomposition Produced %d convex hulls", hullCount );
		for (uint32_t j=0; j<hullCount; j++)
		{
			uint32_t c = RENDER_DEBUG::DebugColors::Red;
			for (uint32_t i=0; i<=j; i++)
			{
				const HACD::HACD_API::Hull *h = HACD::gHACD->getHull(i);

				if ( h )
				{
					if ( i == j )
					{
						gRenderDebug->debugMessage("Hull %d has %d triangles %d vertices and a volume of %0.4f", i+1, h->mTriangleCount, h->mVertexCount, h->mVolume );
					}
					uint32_t color = gRenderDebug->getDebugColor((RENDER_DEBUG::DebugColors::Enum)c);
					gRenderDebug->setCurrentColor(color,0xFFFFFF);


					for (uint32_t i=0; i<h->mTriangleCount; i++)
					{
						uint32_t i1 = h->mIndices[i*3+0];
						uint32_t i2 = h->mIndices[i*3+1];
						uint32_t i3 = h->mIndices[i*3+2];
						const float *p1 = &h->mVertices[i1*3];
						const float *p2 = &h->mVertices[i2*3];
						const float *p3 = &h->mVertices[i3*3];
						gRenderDebug->debugTri(p1,p2,p3);
					}

					c++;
					if ( c == RENDER_DEBUG::DebugColors::NUM_COLORS )
					{
						c = RENDER_DEBUG::DebugColors::Red;
					}
				}
			}
			gRenderDebug->render(1.0f/60.0f,NULL);
		}
	}

	{
		// Next rendern an animated exploded view of the convex hull.
		uint32_t hullCount = HACD::gHACD->getHullCount();
		gRenderDebug->debugMessage("Convex Decomposition Produced %d convex hulls", hullCount );
		for (float distance=0; distance<10; distance+=0.25f)
		{
			uint32_t c = RENDER_DEBUG::DebugColors::Red;
			for (uint32_t i=0; i<hullCount; i++)
			{
				const HACD::HACD_API::Hull *h = HACD::gHACD->getHull(i);
				if ( h )
				{
					// ok..first let's compute the centroid of this convex hull...
					float bmin[3] = { 1e9, 1e9, 1e9 };
					float bmax[3] = { -1e9, -1e9, -1e9 };
					for (uint32_t i=0; i<h->mTriangleCount; i++)
					{
						uint32_t i1 = h->mIndices[i*3+0];
						uint32_t i2 = h->mIndices[i*3+1];
						uint32_t i3 = h->mIndices[i*3+2];

						const float *p1 = &h->mVertices[i1*3];
						const float *p2 = &h->mVertices[i2*3];
						const float *p3 = &h->mVertices[i3*3];

						includeMinMax(p1,bmin,bmax);
						includeMinMax(p2,bmin,bmax);
						includeMinMax(p3,bmin,bmax);

					}

					float center[3];
					center[0] = (bmin[0]+bmax[0])*0.5f;
					center[1] = (bmin[1]+bmax[1])*0.5f;
					center[2] = (bmin[2]+bmax[2])*0.5f;
					float mag = sqrtf(center[0]*center[0]+center[1]*center[1]+center[2]*center[2]);
					float recip = 1;
					if ( mag > 0 )
					{
						recip = 1.0f / mag;
					}

					center[0]*=recip*distance;
					center[1]*=recip*distance;
					center[2]*=recip*distance;


					uint32_t color = gRenderDebug->getDebugColor((RENDER_DEBUG::DebugColors::Enum)c);
					gRenderDebug->setCurrentColor(color,0xFFFFFF);
					for (uint32_t i=0; i<h->mTriangleCount; i++)
					{
						uint32_t i1 = h->mIndices[i*3+0];
						uint32_t i2 = h->mIndices[i*3+1];
						uint32_t i3 = h->mIndices[i*3+2];

						const float *p1 = &h->mVertices[i1*3];
						const float *p2 = &h->mVertices[i2*3];
						const float *p3 = &h->mVertices[i3*3];

						float tp1[3];
						float tp2[3];
						float tp3[3];

						tp1[0] = p1[0]+center[0];
						tp1[1] = p1[1]+center[1];
						tp1[2] = p1[2]+center[2];

						tp2[0] = p2[0]+center[0];
						tp2[1] = p2[1]+center[1];
						tp2[2] = p2[2]+center[2];

						tp3[0] = p3[0]+center[0];
						tp3[1] = p3[1]+center[1];
						tp3[2] = p3[2]+center[2];


						gRenderDebug->debugTri(tp1,tp2,tp3);
					}

					c++;
					if ( c == RENDER_DEBUG::DebugColors::NUM_COLORS )
					{
						c = RENDER_DEBUG::DebugColors::Red;
					}
				}
			}
			gRenderDebug->render(1.0f/60.0f,NULL);
		}
	}



}

class MyCallback : public hacd::ICallback
{
public:
	MyCallback(void)
	{
		mLastPercent = 0xFFFFFFFF;
		mLastMessage = NULL;
	}

	virtual bool Cancelled()
	{
		// Don't have a cancel button in the test console app.
		return false;
	}

	virtual void ReportProgress(const char* message, float progress)
	{
		uint32_t percent = (uint32_t)(progress*100.0f);
		if ( percent != mLastPercent || message != mLastMessage )
		{
			if ( message == mLastMessage )
			{
				printf("%c%c%c%c",8,8,8,8);
				printf("%3d%c", percent,'%' );
			}
			else
			{
				printf("\r\n%s : %3d", message, percent);
			}
			mLastMessage = message;
			mLastPercent = percent;
		}
	}
	const char *	mLastMessage;
	uint32_t		mLastPercent;
};

int main(int argc,const char ** argv)
{
	if ( argc == 1 )
	{
		printf("Usage: TestHACD <wavefront.obj> (options)\r\n");
		printf("\r\n");
		printf("Options:\r\n");
		printf("-v	<count>	: Max Hull Vertices (default 64)\r\n");
		printf("-m	<count>	: Maximum number of hulls output from HACD (default 256)\r\n");
		printf("-merge <count> : Maximum number of hulls after merging the HACD result.\r\n");
		printf("-mergethreshold <volume> : Threshold below which hulls are merged if they are smaller than the given volume.\r\n");
		printf("-c <concavity> : Between 0 and 1 are good ranges to try; default is 0.2.  The smaller the number, the more convex hulls are produced.\r\n");
		printf("-b <backFaceDistanceFactor : The back face distance factor, default is 0.2\r\n");
		printf("-d <decompositionDepth> : Specifies the decomposition depth; uses legacy ACD instead of HACD\r\n");
		printf("-n : Normalize the input mesh.\r\n");
		printf("-f : Use legacy 'fast' version of HACD\r\n");
		printf("-r : Use RenderDebug library for debug visualization.  Windows only feature.\r\n");
		printf("\r\n");
		printf("Example: TestHACD hornbug.obj -c 0.2 -f -m 32\r\n");
		printf("\r\n");
	}
	else
	{
		HACD::HACD_API::Desc desc;
		const char *wavefront = argv[1];
		int scan = 2;


		while ( scan < argc )
		{
			const char *option = argv[scan];
			if ( strcmp(option,"-v") == 0 )
			{
				desc.mMaxHullVertices = getIntArg(scan+1,argc,argv);
				scan+=2;
			}
			else if ( strcmp(option,"-d") == 0 )
			{
				desc.mDecompositionDepth  = getIntArg(scan+1,argc,argv);
				scan+=2;
			}
			else if ( strcmp(option,"-b") == 0 )
			{
				desc.mBackFaceDistanceFactor = getFloatArg(scan+1,argc,argv);
				scan+=2;
			}
			else if ( strcmp(option,"-m") == 0 )
			{
				desc.mMaxHullCount = getIntArg(scan+1,argc,argv);
				scan+=2;
			}
			else if ( strcmp(option,"-merge") == 0 )
			{
				desc.mMaxMergeHullCount = getIntArg(scan+1,argc,argv);
				scan+=2;
			}
			else if ( strcmp(option,"-mergethreshold") == 0 )
			{
				desc.mSmallClusterThreshold = getFloatArg(scan+1,argc,argv);
				scan+=2;
			}
			else if ( strcmp(option,"-c") == 0 )
			{
				desc.mConcavity = getFloatArg(scan+1,argc,argv);
				scan+=2;
			}
			else if ( strcmp(option,"-n") == 0 )
			{
				desc.mNormalizeInputMesh = true;
				scan++;
			}
			else if ( strcmp(option,"-f") == 0 )
			{
				desc.mUseFastVersion = true;
				scan++;
			}
			else if ( strcmp(option,"-r") == 0 )
			{
				getRenderDebugDLL();
				scan++;
			}
			else
			{
				scan++;
			}
		}
		HACD::gHACD = HACD::createHACD_API();
		if  ( HACD::gHACD )
		{
			MyCallback callback;
			WavefrontObj obj;
			unsigned int tcount = obj.loadObj(wavefront);
			if ( tcount )
			{
				desc.mTriangleCount = obj.mTriCount;
				desc.mVertexCount = obj.mVertexCount;
				desc.mIndices = (uint32_t *)obj.mIndices;
				desc.mVertices = obj.mVertices;
			}
			desc.mCallback = static_cast<hacd::ICallback*>(&callback);
			if ( desc.mTriangleCount )
			{
				printf("Performing HACD on %d input triangles.\r\n", desc.mTriangleCount );
				printf("\r\n");
				printf("TriangleCount            : %d\r\n", desc.mTriangleCount );
				printf("VertexCount              : %d\r\n", desc.mVertexCount );
				printf("Concavity                : %0.2f\r\n", desc.mConcavity );
				printf("Max Hull Vertex Count    : %3d\r\n", desc.mMaxHullVertices );
				printf("Max Convex Hulls         : %3d\r\n", desc.mMaxHullCount );
				printf("Max Merged Convex Hulls  : %3d\r\n", desc.mMaxMergeHullCount );
				printf("Merge Threshold          : %0.2f\r\n", desc.mSmallClusterThreshold);
				printf("Back Face Distance Factor: %0.2f\r\n", desc.mBackFaceDistanceFactor );
				printf("Small Cluster Threshold  : %0.2f\r\n", desc.mSmallClusterThreshold );
				if ( desc.mDecompositionDepth )
				{
					printf("DecompositionDepth       : %d\r\n", desc.mDecompositionDepth );
				}

				if ( desc.mNormalizeInputMesh )
				{
					printf("Normalizing input mesh.\r\n");
				}

				if ( desc.mUseFastVersion )
				{
					printf("Using 'fast' version of HACD.\r\n");
				}

				printf("\r\n");

				uint32_t hullCount = HACD::gHACD->performHACD(desc);

				if ( hullCount != 0 )
				{
					printf("\r\n");
					printf("Produced %d output convex hulls. Saving output to 'ConvexDecomposition.obj' for review.\r\n", hullCount );
					FILE *fph = fopen("ConvexDecomposition.obj", "wb");
					if ( fph )
					{
						fprintf(fph,"# Input mesh '%s' produced %d convex hulls.\r\n", wavefront, hullCount );
						uint32_t *baseVertex = new uint32_t[hullCount];
						uint32_t vertexCount = 0;
						for (uint32_t i=0; i<hullCount; i++)
						{
							const HACD::HACD_API::Hull *hull = HACD::gHACD->getHull(i);
							if ( hull )
							{
								baseVertex[i] = vertexCount;
								fprintf(fph,"## Hull %d has %d vertices.\r\n", i+1, hull->mVertexCount );
								for (uint32_t i=0; i<hull->mVertexCount; i++)
								{
									const float *p = &hull->mVertices[i*3];
									fprintf(fph,"v %0.9f %0.9f %0.9f\r\n", p[0], p[1], p[2] );
								}
								vertexCount+=hull->mVertexCount;
							}
						}
						for (uint32_t i=0; i<hullCount; i++)
						{
							const HACD::HACD_API::Hull *hull = HACD::gHACD->getHull(i);
							if ( hull )
							{
								uint32_t startVertex = baseVertex[i];
								printf("# Convex Hull %d contains %d triangles and %d vertices.  Volume: %0.6f\r\n", i+1, hull->mTriangleCount, hull->mVertexCount, hull->mVolume);
								fprintf(fph,"# Convex Hull %d contains %d triangles and %d vertices.  Starting vertex index is: %d Volume: %0.6f\r\n", i+1, hull->mTriangleCount, hull->mVertexCount, startVertex, hull->mVolume);
								for (uint32_t j=0; j<hull->mTriangleCount; j++)
								{
									uint32_t i1 = hull->mIndices[j*3+0]+startVertex+1;
									uint32_t i2 = hull->mIndices[j*3+1]+startVertex+1;
									uint32_t i3 = hull->mIndices[j*3+2]+startVertex+1;
									fprintf(fph,"f %d %d %d\r\n", i1, i2, i3 );
								}
							}
						}

						if ( gRenderDebug )
						{
							debugVisualize(obj);
						}
					}
					else
					{
						printf("Failed to open output file.\r\n");
					}
				}
			}
			else
			{
				printf("Failed to load Wavefront OBJ file '%s'\r\n",wavefront);
			}
		}
		else
		{
			printf("Failed to load the HACD DLL\r\n");
		}
	}
	return 0;
}
